/*-------------------------------------------------------------------------
 *
 * array_expanded.c
 *	  操作扩展数组的基本函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/array_expanded.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/tupmacs.h"
#include "utils/array.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"


/* 扩展对象所需的 "方法" */
static Size fc_EA_get_flat_size(ExpandedObjectHeader *fc_eohptr);
static void fc_EA_flatten_into(ExpandedObjectHeader *fc_eohptr,
							void *fc_result, Size fc_allocated_size);

static const ExpandedObjectMethods EA_methods =
{
	fc_EA_get_flat_size,
	fc_EA_flatten_into
};

/* 其他本地函数 */
static void fc_copy_byval_expanded_array(ExpandedArrayHeader *fc_eah,
									  ExpandedArrayHeader *fc_oldeah);


/* expand_array: 将一个数组 Datum 转换为扩展数组 */  
 /* 扩展对象将是 parentcontext 的一个子对象。 */  
 /* 一些调用者可以提供缓存空间，以避免在调用之间重复查找元素类型数据；如果可以，请传递一个 metacache 指针，确保在第一次调用之前初始化 metacache->element_type 为 InvalidOid。如果不需要跨调用缓存，则将 metacache 传递为 NULL。 */
Datum expand_array(Datum fc_arraydatum, MemoryContext fc_parentcontext,
			 ArrayMetaState *fc_metacache)
{
	ArrayType  *fc_array;
	ExpandedArrayHeader *fc_eah;
	MemoryContext fc_objcxt;
	MemoryContext fc_oldcxt;
	ArrayMetaState fc_fakecache;

	/* 为扩展对象分配私有上下文。我们开始假设数组不会很大；但是，如果它确实增长很多，请不要限制 aset.c 的大上下文行为。 */
	fc_objcxt = AllocSetContextCreate(fc_parentcontext,
								   "expanded array",
								   ALLOCSET_START_SMALL_SIZES);

	/* 设置扩展数组头 */
	fc_eah = (ExpandedArrayHeader *)
		MemoryContextAlloc(fc_objcxt, sizeof(ExpandedArrayHeader));

	EOH_init_header(&fc_eah->hdr, &EA_methods, fc_objcxt);
	fc_eah->ea_magic = EA_MAGIC;

	/* 如果源是一个扩展数组，我们可能能够进行优化 */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_arraydatum)))
	{
		ExpandedArrayHeader *fc_oldeah = (ExpandedArrayHeader *) DatumGetEOHP(fc_arraydatum);

		Assert(fc_oldeah->ea_magic == EA_MAGIC);

		/* 更新调用者的缓存（如果提供）；这次我们不需要它，但下次调用可能是针对非扩展源数组。此外，如果调用者没有提供缓存区域，请使用一些本地存储进行缓存，从而避免在我们回落到平面复制代码路径时查找目录。 */
		if (fc_metacache == NULL)
			fc_metacache = &fc_fakecache;
		fc_metacache->element_type = fc_oldeah->element_type;
		fc_metacache->typlen = fc_oldeah->typlen;
		fc_metacache->typbyval = fc_oldeah->typbyval;
		fc_metacache->typalign = fc_oldeah->typalign;

		/* 如果元素类型是按值传递，并且我们具有 Datum-array 表示，只需复制源的元数据和 Datum/isnull 数组。如果原始的平面数组存在，实际上并没有提供额外的信息，因此我们无需复制它。 */
		if (fc_oldeah->typbyval && fc_oldeah->dvalues != NULL)
		{
			fc_copy_byval_expanded_array(fc_eah, fc_oldeah);
			/* 返回指向扩展数组的 R/W 指针 */
			return EOHPGetRWDatum(&fc_eah->hdr);
		}

		/* 否则，我们要么只有平面表示，要么元素是按引用传递。在这两种情况下，最佳做法似乎是将源复制为平面表示，然后在必要时再去构造它。对于按引用传递的情况，我们或许可以通过自定义代码节省一些循环，这些代码在复制值的同时生成去构造的表示，但这将导致大量额外代码以获得相对微小的收益。因此，回落到平面源代码路径。 */
	}

	/* 从源数组中去构造并复制到私有上下文，作为平面数组。 */  
 /* 请注意，这种编码在私有上下文中存在泄漏一些内存的风险，如果我们必须从 TOAST 表中获取数据；然而，实验表明泄漏是微不足道的。用这种方式可以节省一个复制步骤，这似乎是值得的，尤其是当数组足够大以需要外部存储时。 */
	fc_oldcxt = MemoryContextSwitchTo(fc_objcxt);
	fc_array = DatumGetArrayTypePCopy(fc_arraydatum);
	MemoryContextSwitchTo(fc_oldcxt);

	fc_eah->ndims = ARR_NDIM(fc_array);
	/* 注意这些指针指向 fvalue 头部！ */
	fc_eah->dims = ARR_DIMS(fc_array);
	fc_eah->lbound = ARR_LBOUND(fc_array);

	/* 保存数组的元素类型数据，以便以后可能使用 */
	fc_eah->element_type = ARR_ELEMTYPE(fc_array);
	if (fc_metacache && fc_metacache->element_type == fc_eah->element_type)
	{
		/* 我们有一个有效的表示数据缓存 */
		fc_eah->typlen = fc_metacache->typlen;
		fc_eah->typbyval = fc_metacache->typbyval;
		fc_eah->typalign = fc_metacache->typalign;
	}
	else
	{
		/* 不，所以查找它 */
		get_typlenbyvalalign(fc_eah->element_type,
							 &fc_eah->typlen,
							 &fc_eah->typbyval,
							 &fc_eah->typalign);
		/* 如果提供，更新缓存 */
		if (fc_metacache)
		{
			fc_metacache->element_type = fc_eah->element_type;
			fc_metacache->typlen = fc_eah->typlen;
			fc_metacache->typbyval = fc_eah->typbyval;
			fc_metacache->typalign = fc_eah->typalign;
		}
	}

	/* 我们现在不制作去构造的表示 */
	fc_eah->dvalues = NULL;
	fc_eah->dnulls = NULL;
	fc_eah->dvalueslen = 0;
	fc_eah->nelems = 0;
	fc_eah->flat_size = 0;

	/* 记住我们有一个平面表示 */
	fc_eah->fvalue = fc_array;
	fc_eah->fstartptr = ARR_DATA_PTR(fc_array);
	fc_eah->fendptr = ((char *) fc_array) + ARR_SIZE(fc_array);

	/* 返回指向扩展数组的 R/W 指针 */
	return EOHPGetRWDatum(&fc_eah->hdr);
}

/* expand_array() 的辅助程序：复制按值传递的 Datum-array 表示 */
static void fc_copy_byval_expanded_array(ExpandedArrayHeader *fc_eah,
						  ExpandedArrayHeader *fc_oldeah)
{
	MemoryContext fc_objcxt = fc_eah->hdr.eoh_context;
	int			fc_ndims = fc_oldeah->ndims;
	int			fc_dvalueslen = fc_oldeah->dvalueslen;

	/* 复制数组的维度信息 */
	fc_eah->ndims = fc_ndims;
	/* 我们可以用一个 palloc 分配两个维度数组 */
	fc_eah->dims = (int *) MemoryContextAlloc(fc_objcxt, fc_ndims * 2 * sizeof(int));
	fc_eah->lbound = fc_eah->dims + fc_ndims;
	/* .. 但不要假设源的数组是连续的 */
	memcpy(fc_eah->dims, fc_oldeah->dims, fc_ndims * sizeof(int));
	memcpy(fc_eah->lbound, fc_oldeah->lbound, fc_ndims * sizeof(int));

	/* 复制元素类型数据 */
	fc_eah->element_type = fc_oldeah->element_type;
	fc_eah->typlen = fc_oldeah->typlen;
	fc_eah->typbyval = fc_oldeah->typbyval;
	fc_eah->typalign = fc_oldeah->typalign;

	/* 复制去构造的表示 */
	fc_eah->dvalues = (Datum *) MemoryContextAlloc(fc_objcxt,
												fc_dvalueslen * sizeof(Datum));
	memcpy(fc_eah->dvalues, fc_oldeah->dvalues, fc_dvalueslen * sizeof(Datum));
	if (fc_oldeah->dnulls)
	{
		fc_eah->dnulls = (bool *) MemoryContextAlloc(fc_objcxt,
												  fc_dvalueslen * sizeof(bool));
		memcpy(fc_eah->dnulls, fc_oldeah->dnulls, fc_dvalueslen * sizeof(bool));
	}
	else
		fc_eah->dnulls = NULL;
	fc_eah->dvalueslen = fc_dvalueslen;
	fc_eah->nelems = fc_oldeah->nelems;
	fc_eah->flat_size = fc_oldeah->flat_size;

	/* 我们不制作平面表示 */
	fc_eah->fvalue = NULL;
	fc_eah->fstartptr = NULL;
	fc_eah->fendptr = NULL;
}

/* 扩展数组的 get_flat_size 方法 */
static Size fc_EA_get_flat_size(ExpandedObjectHeader *fc_eohptr)
{
	ExpandedArrayHeader *fc_eah = (ExpandedArrayHeader *) fc_eohptr;
	int			fc_nelems;
	int			fc_ndims;
	Datum	   *fc_dvalues;
	bool	   *fc_dnulls;
	Size		fc_nbytes;
	int			fc_i;

	Assert(fc_eah->ea_magic == EA_MAGIC);

	/* 如果我们有一个有效的扁平值，则很简单 */
	if (fc_eah->fvalue)
		return ARR_SIZE(fc_eah->fvalue);

	/* 如果我们有缓存的大小值，请相信它 */
	if (fc_eah->flat_size)
		return fc_eah->flat_size;

	/*
	 * 通过检查 dvalues/dnulls 来计算所需空间。请注意，如果 dnulls 不是 NULL，则结果
	 * 数组将具有空值位图，即使现在数组实际上不包含任何空值。
	 */
	fc_nelems = fc_eah->nelems;
	fc_ndims = fc_eah->ndims;
	Assert(fc_nelems == ArrayGetNItems(fc_ndims, fc_eah->dims));
	fc_dvalues = fc_eah->dvalues;
	fc_dnulls = fc_eah->dnulls;
	fc_nbytes = 0;
	for (fc_i = 0; fc_i < fc_nelems; fc_i++)
	{
		if (fc_dnulls && fc_dnulls[fc_i])
			continue;
		fc_nbytes = att_addlength_datum(fc_nbytes, fc_eah->typlen, fc_dvalues[fc_i]);
		fc_nbytes = att_align_nominal(fc_nbytes, fc_eah->typalign);
		/* 检查总请求的溢出 */
		if (!AllocSizeIsValid(fc_nbytes))
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("array size exceeds the maximum allowed (%d)",
							(int) MaxAllocSize)));
	}

	if (fc_dnulls)
		fc_nbytes += ARR_OVERHEAD_WITHNULLS(fc_ndims, fc_nelems);
	else
		fc_nbytes += ARR_OVERHEAD_NONULLS(fc_ndims);

	/* 下次缓存 */
	fc_eah->flat_size = fc_nbytes;

	return fc_nbytes;
}

/*
 * 展平方法用于扩展数组
 */
static void fc_EA_flatten_into(ExpandedObjectHeader *fc_eohptr,
				void *fc_result, Size fc_allocated_size)
{
	ExpandedArrayHeader *fc_eah = (ExpandedArrayHeader *) fc_eohptr;
	ArrayType  *fc_aresult = (ArrayType *) fc_result;
	int			fc_nelems;
	int			fc_ndims;
	int32		fc_dataoffset;

	Assert(fc_eah->ea_magic == EA_MAGIC);

	/* 如果我们有一个有效的扁平值，则很简单 */
	if (fc_eah->fvalue)
	{
		Assert(fc_allocated_size == ARR_SIZE(fc_eah->fvalue));
		memcpy(fc_result, fc_eah->fvalue, fc_allocated_size);
		return;
	}

	/* 否则分配应与先前的 get_flat_size 结果匹配 */
	Assert(fc_allocated_size == fc_eah->flat_size);

	/* 从 dvalues/dnulls 填充结果数组 */
	fc_nelems = fc_eah->nelems;
	fc_ndims = fc_eah->ndims;

	if (fc_eah->dnulls)
		fc_dataoffset = ARR_OVERHEAD_WITHNULLS(fc_ndims, fc_nelems);
	else
		fc_dataoffset = 0;			/* 表示没有空位图的标记 */

	/* 我们必须确保任何填充空间为零填充 */
	memset(fc_aresult, 0, fc_allocated_size);

	SET_VARSIZE(fc_aresult, fc_allocated_size);
	fc_aresult->ndim = fc_ndims;
	fc_aresult->dataoffset = fc_dataoffset;
	fc_aresult->elemtype = fc_eah->element_type;
	memcpy(ARR_DIMS(fc_aresult), fc_eah->dims, fc_ndims * sizeof(int));
	memcpy(ARR_LBOUND(fc_aresult), fc_eah->lbound, fc_ndims * sizeof(int));

	CopyArrayEls(fc_aresult,
				 fc_eah->dvalues, fc_eah->dnulls, fc_nelems,
				 fc_eah->typlen, fc_eah->typbyval, fc_eah->typalign,
				 false);
}

/*
 * 参数获取支持代码
 */

/*
 * DatumGetExpandedArray：从输入参数获取可写的扩展数组
 *
 * 注意：如果输入是一个读/写指针，这将返回输入
 * 参数；因此调用者必须确保他们的更改是“安全的”，即他们不能将数组
 * 保留在损坏状态中。
 */
ExpandedArrayHeader * DatumGetExpandedArray(Datum fc_d)
{
	/* 如果它已经是一个可写的扩展数组，直接返回它 */
	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_d)))
	{
		ExpandedArrayHeader *fc_eah = (ExpandedArrayHeader *) DatumGetEOHP(fc_d);

		Assert(fc_eah->ea_magic == EA_MAGIC);
		return fc_eah;
	}

	/* 否则以更困难的方式扩展 */
	fc_d = expand_array(fc_d, CurrentMemoryContext, NULL);
	return (ExpandedArrayHeader *) DatumGetEOHP(fc_d);
}

/*
 * 与上面一样，当调用者有能力缓存元素类型信息时
 */
ExpandedArrayHeader * DatumGetExpandedArrayX(Datum fc_d, ArrayMetaState *fc_metacache)
{
	/* 如果它已经是一个可写的扩展数组，直接返回它 */
	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_d)))
	{
		ExpandedArrayHeader *fc_eah = (ExpandedArrayHeader *) DatumGetEOHP(fc_d);

		Assert(fc_eah->ea_magic == EA_MAGIC);
		/* 如果提供，更新缓存 */
		if (fc_metacache)
		{
			fc_metacache->element_type = fc_eah->element_type;
			fc_metacache->typlen = fc_eah->typlen;
			fc_metacache->typbyval = fc_eah->typbyval;
			fc_metacache->typalign = fc_eah->typalign;
		}
		return fc_eah;
	}

	/* 否则如果有，使用调用者的缓存进行扩展 */
	fc_d = expand_array(fc_d, CurrentMemoryContext, fc_metacache);
	return (ExpandedArrayHeader *) DatumGetEOHP(fc_d);
}

/*
 * DatumGetAnyArrayP：返回扩展数组或解压的 varlena
 * 数组。结果必须不得就地修改。
 */
AnyArrayType *
DatumGetAnyArrayP(Datum fc_d)
{
	ExpandedArrayHeader *fc_eah;

	/*
	 * 如果它是扩展数组（RW 或 RO），则返回头指针。
	 */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_d)))
	{
		fc_eah = (ExpandedArrayHeader *) DatumGetEOHP(fc_d);
		Assert(fc_eah->ea_magic == EA_MAGIC);
		return (AnyArrayType *) fc_eah;
	}

	/* 否则根据需要进行常规解压 */
	return (AnyArrayType *) PG_DETOAST_DATUM(fc_d);
}

/*
 * 创建扩展数组对象的 Datum/isnull 表示，
 * 如果我们之前没有这样做的话
 */
void deconstruct_expanded_array(ExpandedArrayHeader *fc_eah)
{
	if (fc_eah->dvalues == NULL)
	{
		MemoryContext fc_oldcxt = MemoryContextSwitchTo(fc_eah->hdr.eoh_context);
		Datum	   *fc_dvalues;
		bool	   *fc_dnulls;
		int			fc_nelems;

		fc_dnulls = NULL;
		deconstruct_array(fc_eah->fvalue,
						  fc_eah->element_type,
						  fc_eah->typlen, fc_eah->typbyval, fc_eah->typalign,
						  &fc_dvalues,
						  ARR_HASNULL(fc_eah->fvalue) ? &fc_dnulls : NULL,
						  &fc_nelems);

		/*
		 * 仅在成功完成此步骤后更新头部。如果
		 * deconstruct_array 中途失败，最糟糕的后果
		 * 是对象上下文中一些泄漏的内存。如果调用者在稍后的时刻失败
		 * 也没关系，因为解构的表示无论如何都是有效的。
		 */
		fc_eah->dvalues = fc_dvalues;
		fc_eah->dnulls = fc_dnulls;
		fc_eah->dvalueslen = fc_eah->nelems = fc_nelems;
		MemoryContextSwitchTo(fc_oldcxt);
	}
}
